package com.ttg.common.utils.sm;

import org.bouncycastle.asn1.*;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;


/**
 * 国密算法的签名、验签
 *
 * @author
 * @类名称 SM2VerifySign
 * @remark
 * @date 2017-8-16
 */
class SM2VerifySign {

    private static final Logger LOGGER = LoggerFactory.getLogger(SM2VerifySign.class);

    /**
     * 默认USERID
     */
    public static String USER_ID = "1234567812345678";

    /**
     * 私钥签名
     * 使用SM3进行对明文数据计算一个摘要值
     *
     * @param privatekey 私钥
     * @param sourceData 明文数据
     * @return 签名后的值
     * @throws Exception
     */
    public static SM2SignVO sign2SM2(byte[] privatekey, byte[] sourceData) throws Exception {
        return sign2SM2(privatekey, sourceData, USER_ID.getBytes(StandardCharsets.UTF_8));
    }


    /**
     * 私钥签名
     *
     * @param privatekey 私钥
     * @param sourceData 待签数据
     * @param userId     用户标识
     * @return 签名结构
     * @throws Exception
     */
    public static SM2SignVO sign2SM2(byte[] privatekey, byte[] sourceData, byte[] userId) throws Exception {
        SM2SignVO sm2SignVO = new SM2SignVO();
        sm2SignVO.setSm2Type("sign");
        SM2Factory factory = SM2Factory.getInstance();
        BigInteger userD = new BigInteger(1, privatekey);
//		System.out.println("userD:"+userD.toString());
        sm2SignVO.setSm2Userd(userD.toString(16));

        ECPoint userKey = factory.eccPointG.multiply(userD);
//		System.out.println("userKey2:"+userKey.toString());
//		System.out.println("椭圆曲线点X: "+ userKey.getXCoord().toBigInteger().toString(16));
//		System.out.println("椭圆曲线点Y: "+ userKey.getYCoord().toBigInteger().toString(16));
        sm2SignVO.setxCoord(userKey.getXCoord().toBigInteger().toString(16));
        sm2SignVO.setyCoord(userKey.getYCoord().toBigInteger().toString(16));

        SM3Digest sm3Digest = new SM3Digest();
        byte[] z = factory.sm2GetZ(userId, userKey);
//		System.out.println("SM3摘要Z: " + Util.getHexString(z));
//		System.out.println("被加密数据的16进制: " + Util.getHexString(sourceData));
        sm2SignVO.setSm3Z(Util.getHexString(z));
        sm2SignVO.setSignExpress(Util.getHexString(sourceData));

        sm3Digest.update(z, 0, z.length);
        sm3Digest.update(sourceData, 0, sourceData.length);
        byte[] md = new byte[32];
        sm3Digest.doFinal(md, 0);

//		System.out.println("SM3摘要值: " + Util.getHexString(md));
//		System.out.println("userKey:"+userKey.toString());
        sm2SignVO.setSm3Digest(Util.getHexString(md));

        SM2Result sm2Result = new SM2Result();
        factory.sm2Sign(md, userD, userKey, sm2Result);
//		System.out.println("r: " + sm2Result.r.toString(16));
//		System.out.println("s: " + sm2Result.s.toString(16));
        sm2SignVO.setSignR(sm2Result.r.toString(16));
        sm2SignVO.setSignS(sm2Result.s.toString(16));

        ASN1Integer dR = new ASN1Integer(sm2Result.r);
        ASN1Integer dS = new ASN1Integer(sm2Result.s);
        ASN1EncodableVector v2 = new ASN1EncodableVector();
        v2.add(dR);
        v2.add(dS);

        DERSequence sign = new DERSequence(v2);


        //格式修正
        byte signBin[] = sign.getEncoded();

//        if (signBin[1] == 0x43) {//因为对接信安的标准，当标准不一致的需要需要调整补码格式
//            /**
//             *  测试加的 isNeedSend
//             */
//            isNeedSend = true;
//            LOGGER.error("signBin ==0x43 :" + CryptoUtil.bin2hex(signBin) + "  need change format");
//            ByteBuffer bb = ByteBuffer.allocate(70);
//            bb.put(signBin[0]);
//            bb.put((byte) 0x44);
//            //数据R
//            int r_size = (signBin[3] & 0xff);
//            if (signBin[3] == 0x1f) {
//                bb.put((byte) 0x02);
//                bb.put((byte) 0x20);
//                bb.put((byte) 0x00);
//                bb.put(ArrayUtil.getByteDataFromLength(signBin, 4, 31));
//            } else {
//                bb.put(ArrayUtil.getByteDataFromLength(signBin, 2, 2 + r_size));
//            }
//            //数据S
//            int s_size_position = 2 + 2 + r_size + 2 - 1;
//            int s_size = signBin[s_size_position] & 0xff;
//            if (signBin[s_size_position] == 0x1f) {//补0
//                bb.put((byte) 0x02);
//                bb.put((byte) 0x20);
//                bb.put((byte) 0x00);
//                bb.put(ArrayUtil.getByteDataFromLength(signBin, r_size + 6, 31));
//            } else {
//                bb.put((byte) 0x02);
//                bb.put((byte) s_size);
//                bb.put(ArrayUtil.getByteDataFromLength(signBin, r_size + 6, s_size));
//            }
//            signBin = bb.array();
//        } else
        if (signBin[1] <= 0x43) {

            LOGGER.error("signBin < 0x43 : " + CryptoUtil.bin2hex(signBin) + "  need change format");
            int r_size = (signBin[3] & 0xff);
            int s_size_position = 2 + 2 + r_size + 2 - 1;
            int s_size = signBin[s_size_position] & 0xff;
            int r_need_padding = 0;
            int s_need_padding = 0;
            if (r_size < 32) {
                r_need_padding = 32 - r_size;
            }
            if (s_size < 32) {
                s_need_padding = 32 - s_size;
            }
            ByteBuffer bb = ByteBuffer.allocate(r_size + r_need_padding + s_size + s_need_padding + 6);
            bb.put(signBin[0]);
            bb.put((byte) (r_size + r_need_padding + s_size + s_need_padding + 4));

            //大数R补数
            if (signBin[3] < 0x20) {
                bb.put((byte) 0x02);
                bb.put((byte) 0x20);
                for (int i = 0; i < r_need_padding; i++)
                    bb.put((byte) 0x00);
                bb.put(ArrayUtil.getByteDataFromLength(signBin, 4, r_size));
            } else {
                bb.put(ArrayUtil.getByteDataFromLength(signBin, 2, 2 + r_size));
            }
            //大数S补数
            if (signBin[s_size_position] < 0x20) {//补0
                bb.put((byte) 0x02);
                bb.put((byte) 0x20);
                for (int i = 0; i < s_need_padding; i++)
                    bb.put((byte) 0x00);
                bb.put(ArrayUtil.getByteDataFromLength(signBin, r_size + 6, s_size));
            } else {
                bb.put((byte) 0x02);
                bb.put((byte) s_size);
                bb.put(ArrayUtil.getByteDataFromLength(signBin, r_size + 6, s_size));
            }
            signBin = bb.array();
        } else if (signBin[1] == 0x44) {
            //0x44一般是正常情况，特殊情况会出现两边不等，这时候需要修复成0x45
            if (signBin[3] != 0x20) {
                LOGGER.error("signBin ==0x44 :" + CryptoUtil.bin2hex(signBin) + "  need change format");
                //如果R的长度不是0x20,那么两边肯定是不对称
//                if(r_size!=32 || s_size!=32){
                int r_size = (signBin[3] & 0xff);
                int s_size_position = 2 + 2 + r_size + 2 - 1;
                int s_size = signBin[s_size_position] & 0xff;
                //其中一个不足64需要补位
                ByteBuffer bb = ByteBuffer.allocate(71);
                bb.put(signBin[0]);
                bb.put((byte) 0x45);
                //判断大数R
                if (signBin[3] == 0x1f) {
                    bb.put((byte) 0x02);
                    bb.put((byte) 0x20);
                    bb.put((byte) 0x00);
                    bb.put(ArrayUtil.getByteDataFromLength(signBin, 4, 31));
                } else {
                    bb.put(ArrayUtil.getByteDataFromLength(signBin, 2, 2 + r_size));
                }
                //判断大数S
                if (signBin[s_size_position] == 0x1f) {//补0
                    bb.put((byte) 0x02);
                    bb.put((byte) 0x20);
                    bb.put((byte) 0x00);
                    bb.put(ArrayUtil.getByteDataFromLength(signBin, r_size + 6, 31));
                } else {
                    bb.put((byte) 0x02);
                    bb.put((byte) s_size);
                    bb.put(ArrayUtil.getByteDataFromLength(signBin, r_size + 6, s_size));
                }

                signBin = bb.array();
            }


        }


//		if(signBin[1]==0x44) {
//         String result = new BASE64Encoder().encodeBuffer(signBin).replace("\r\n", "").replace("\n", "");
        String result = CryptoUtil.bin2hex(signBin);
        sm2SignVO.setSm2Sign(result);
//		}else{
//			LOGGER.error("sign Error not 0x44");
//			sm2SignVO.setSm2_sign(null);
//		}


        return sm2SignVO;
    }

    /**
     * 验证签名
     *
     * @param publicKey  公钥信息
     * @param sourceData 密文信息
     * @param signData   签名信息
     * @return 验签的对象 包含了相关参数和验签结果
     */
    @SuppressWarnings("unchecked")
    public static SM2SignVO verifySignSM2(byte[] publicKey, byte[] sourceData, byte[] signData) {
        return verifySignSM2(publicKey, sourceData, signData, USER_ID.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 验证签名
     *
     * @param publicKey  公钥
     * @param sourceData 待验证消息
     * @param signData   签名值
     * @param userId     用户表示
     * @return
     */
    public static SM2SignVO verifySignSM2(byte[] publicKey, byte[] sourceData, byte[] signData, byte[] userId) {
        try {
            byte[] formatedPubKey;
            SM2SignVO verifyVo = new SM2SignVO();
            verifyVo.setSm2Type("verify");
            if (publicKey.length == 64) {
                // 添加一字节标识，用于ECPoint解析
                formatedPubKey = new byte[65];
                formatedPubKey[0] = 0x04;
                System.arraycopy(publicKey, 0, formatedPubKey, 1, publicKey.length);
            } else {
                formatedPubKey = publicKey;
            }
            SM2Factory factory = SM2Factory.getInstance();
            ECPoint userKey = factory.eccCurve.decodePoint(formatedPubKey);
//		System.out.println("userKey:"+userKey.toString());
            SM3Digest sm3Digest = new SM3Digest();
            byte[] z = factory.sm2GetZ(userId, userKey);
//		System.out.println("SM3摘要Z: " + Util.getHexString(z));
            verifyVo.setSm3Z(Util.getHexString(z));
            sm3Digest.update(z, 0, z.length);
            sm3Digest.update(sourceData, 0, sourceData.length);
            byte[] md = new byte[32];
            sm3Digest.doFinal(md, 0);
//		System.out.println("SM3摘要值: " + Util.getHexString(md));
            verifyVo.setSm3Digest(Util.getHexString(md));
//            ByteArrayInputStream bis = new ByteArrayInputStream(signData);
//            ASN1InputStream dis = new ASN1InputStream(bis);
            SM2Result sm2Result = new SM2Result();
//			ASN1Primitive derObj = dis.readObject();
////			System.out.println("encodeCode"+ CryptoUtil.bin2hex(derObj.getEncoded()));
//			Enumeration<ASN1Integer> e = ((ASN1Sequence)derObj).getObjects();
//			BigInteger r = ((ASN1Integer) e.nextElement()).getValue();
//			BigInteger s = ((ASN1Integer) e.nextElement()).getValue();
//			sm2Result = new SM2Result();


            /**
             * 20190521新增
             */
            BigInteger r = null;
            BigInteger s = null;
            boolean isNeedTranslate = false;

            if (signData[1] > 0x44) {
                isNeedTranslate = true;
            } else if (signData[1] == 0x44) {
                int r_size = signData[3] & 0xff;

                if (signData[4] == 0x00 || signData[6 + r_size] == 0x00) {
                    //存在补码
                    isNeedTranslate = true;
                }
            }
            if (!isNeedTranslate) {
                ByteArrayInputStream bis = new ByteArrayInputStream(signData);
                ASN1InputStream dis = new ASN1InputStream(bis);
                ASN1Primitive derObj = dis.readObject();
                Enumeration<ASN1Integer> e = ((ASN1Sequence) derObj).getObjects();
                r = ((ASN1Integer) e.nextElement()).getValue();
                s = ((ASN1Integer) e.nextElement()).getValue();

            } else {//如果是被补码的格式有可能无法正常解析，需要手动去解析数据
                int r_size = (signData[3] & 0xff);
                int s_size_position = 2 + 2 + r_size + 2 - 1;
                int s_size = signData[s_size_position] & 0xff;
                //其中一个不足64需要补位
                byte[] r_byte = ArrayUtil.getByteDataFromLength(signData, 4, r_size);
                byte[] s_byte = ArrayUtil.getByteDataFromLength(signData, r_size + 6, s_size);
                r = new BigInteger(1, r_byte);
                s = new BigInteger(1, s_byte);

            }


            sm2Result.r = r;
            sm2Result.s = s;
//			System.out.println("vr: " + sm2Result.r.toString(16));
//			System.out.println("vs: " + sm2Result.s.toString(16));
            verifyVo.setVerifyR(sm2Result.r.toString(16));
            verifyVo.setVerifyS(sm2Result.s.toString(16));
            factory.sm2Verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result);
            boolean verifyFlag = sm2Result.r.equals(sm2Result.R);
            verifyVo.setVerify(verifyFlag);
            return verifyVo;
        } catch (IllegalArgumentException e) {
            return null;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 使用 Sm2 进行签名验证(主要在支付时候用)
     *
     * @param publicKey  公钥
     * @param sourceData 待验证消除
     * @param r_sq       签名值r
     * @param s_sq       签名值s
     * @return
     */
    public static SM2SignVO verifySignSM2NoASN1(byte[] publicKey, byte[] sourceData, BigInteger r_sq, BigInteger s_sq) {
        return verifySignSM2NoASN1(publicKey, sourceData, USER_ID.getBytes(StandardCharsets.UTF_8), r_sq, s_sq);
    }

    /**
     * 使用 Sm2 进行签名验证
     *
     * @param publicKey  公钥
     * @param sourceData 待验证消除
     * @param userId     用户标识
     * @param r_sq       签名值r
     * @param s_sq       签名值s
     * @return
     */
    public static SM2SignVO verifySignSM2NoASN1(byte[] publicKey, byte[] sourceData, byte[] userId, BigInteger r_sq, BigInteger s_sq) {
        SM2SignVO verifyVo = new SM2SignVO();
        SM2Factory factory = SM2Factory.getInstance();
        if (r_sq.compareTo(BigInteger.ONE) < 0 || r_sq.compareTo(SM2Factory.n) > 0) {
            verifyVo.setVerify(false);
            return verifyVo;
        }
        if (s_sq.compareTo(BigInteger.ONE) < 0 || s_sq.compareTo(SM2Factory.n) > 0) {
            verifyVo.setVerify(false);
            return verifyVo;
        }
        byte[] formatedPubKey;
        verifyVo.setSm2Type("verify");
        if (publicKey.length == 64) {
            // 添加一字节标识，用于ECPoint解析
            formatedPubKey = new byte[65];
            formatedPubKey[0] = 0x04;
            System.arraycopy(publicKey, 0, formatedPubKey, 1, publicKey.length);
        } else {
            formatedPubKey = publicKey;
        }
        ECPoint userKey = factory.eccCurve.decodePoint(formatedPubKey);
        SM3Digest sm3Digest = new SM3Digest();
        byte[] z = factory.sm2GetZ(userId, userKey);
        verifyVo.setSm3Z(Util.getHexString(z));
        sm3Digest.update(z, 0, z.length);
        sm3Digest.update(sourceData, 0, sourceData.length);
        byte[] md = new byte[32];
        sm3Digest.doFinal(md, 0);
        verifyVo.setSm3Digest(Util.getHexString(md));
        SM2Result sm2Result = new SM2Result();
        sm2Result.r = r_sq;
        sm2Result.s = s_sq;
        factory.sm2Verify(md, userKey, sm2Result.r, sm2Result.s, sm2Result);
        boolean verifyFlag = sm2Result.r.equals(sm2Result.R);
        verifyVo.setVerify(verifyFlag);
        return verifyVo;

    }
}
